package org.openmrs.module.basicmodule.DSS.Interpreter;

import org.openmrs.module.basicmodule.DSS.ast.BlockTree;
import org.openmrs.module.basicmodule.DSS.ast.LiteralTree;
import org.openmrs.module.basicmodule.DSS.ast.AST;
import org.openmrs.module.basicmodule.DSS.ast.IdTree;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Time.TimeFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Time.AddMonthsFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Time.RecentTimeItemFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Time.CurrentTimeFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Time.OldestTimeItemFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Time.BeforeFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Time.AddDaysFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.List.SortTimeFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.List.FirstFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.List.LastFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.List.MergeFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.List.SortDataFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Database.ReadFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Database.ReadLatestEncounterFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.Database.ReadInitialEncounterFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.IsStringFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.IsBooleanFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.AlertFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.WithinFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.IsIntFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.LengthFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.IsDateFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.IsListFunction;
import org.openmrs.module.basicmodule.DSS.Interpreter.Function.IsFloatFunction;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openmrs.module.basicmodule.DSS.visitor.ASTVisitor;

/**
 *
 * @author Stephen, David
 */
public class InterpreterVisitor extends ASTVisitor {

    public static int patientId = 0;
    
    public static void updatePatientId(int id) {
        InterpreterVisitor.patientId = id;
    }
    
    public InterpreterVisitor() {
        
    }
    
    public InterpreterVisitor(int PatientId) {
        updatePatientId(PatientId);
    }
    
    @Override
    public Object visitProgramTree(AST t) {
        SymbolTable.Scope.init();
        loadIntrinsicFunctions();
        visitKids(t);
        return null;
    }

    @Override
    public Object visitBlockTree(AST t) {
        SymbolTable.Scope.newScope();
        visitKids(t);
        SymbolTable.Scope.closeScope();
        return null;
    }

    @Override
    public Object visitFunctionDeclTree(AST t) {
        return new FunctionDeclaration(t, this).execute();
    }

    @Override
    public Object visitObjectDeclTree(AST t) {
        return null;
    }

    @Override
    public Object visitCallTree(AST t) {
        return new CallFunction(t, this).execute();
    }

    @Override
    public Object visitFormalsTree(AST t) {
        System.out.println("Formals");
        return null;
    }

    @Override
    public Object visitIfTree(AST t) {
        boolean cond = (Boolean) t.getKid(1).accept(this);
        if (cond) {
            t.getKid(2).accept(this);
        } else if (t.getKid(3) != null) {
            t.getKid(3).accept(this);
        }
        return null;
    }

    @Override
    public Object visitWhileTree(AST t) {
        boolean cond = (Boolean) t.getKid(1).accept(this);
        BlockTree block = (BlockTree) t.getKid(2);
        while (cond) {
            block.accept(this);
        }
        return null;
    }

    @Override
    public Object visitForTree(AST t) {
        try {
            if (t.getKid(2).getClass() == Class.forName("ast.IdTree")) {
                ArrayList al = (ArrayList) SymbolTable.Scope.getVarValue(((IdTree)t.getKid(2)).getSymbol().toString());
                for(int i =0 ;i < al.size(); i++){

                    String var = ((IdTree)t.getKid(1)).getSymbol().toString();
                    SymbolTable.Scope.addSymbol(var, al.get(i), "object");

                    t.getKid(3).accept(this);
                }

            }else if(t.getKid(2).getClass() == Class.forName("ast.ListTree")) {
                ArrayList al = (ArrayList)t.getKid(2).accept(this);
                for(int i =0 ;i < al.size(); i++){

                    String var = ((IdTree)t.getKid(1)).getSymbol().toString();
                    SymbolTable.Scope.addSymbol(var, al.get(i), "object");

                    t.getKid(3).accept(this);
                }
            }
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(InterpreterVisitor.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    @Override
    public Object visitReturnTree(AST t) {
        Object ret = t.getKid(1).accept(this);
        return ret;
    }

    @Override
    public Object visitAssignTree(AST t) {
        return new Assign(t, this).execute();
    }

    @Override
    public Object visitLiteralTree(AST t) {
        return ((LiteralTree) t).getSymbol().toString();
    }

    @Override
    public Object visitIdTree(AST t) {
        String varName = ((IdTree) t).getSymbol().toString();
        return SymbolTable.Scope.getVarValue(varName);
    }

    @Override
    public Object visitObjectTree(AST t) {
        return null;        
    }

    @Override
    public Object visitOpTree(AST t) {
        return new Operator(t, this).execute();
    }

    @Override
    public Object visitFieldRefTree(AST t) {
        String obj = ((IdTree) t.getKid(1)).getSymbol().toString();
        String field = ((IdTree) t.getKid(2)).getSymbol().toString();
        return SymbolTable.Scope.getVarValue(obj + "." + field);
    }

    @Override
    public Object visitListTree(AST t) {
        ArrayList myList = new ArrayList();
        for (int i = 1; i <= t.kidCount(); i++) {
            Object value = t.getKid(i).accept(this);
            myList.add(value);
        }
        return myList;
    }

    @Override
    public Object visitElsifTree(AST t) {
        boolean cond = (Boolean) t.getKid(1).accept(this);
        if (cond) {
            t.getKid(2).accept(this);
        } else if (t.getKid(3) != null) {
            t.getKid(3).accept(this);
        }
        return null;
    }

    private void loadIntrinsicFunctions() {
        SymbolTable.Scope.addFunction("alert", new AlertFunction());
        SymbolTable.Scope.addFunction("first", new FirstFunction());
        SymbolTable.Scope.addFunction("last", new LastFunction());
        SymbolTable.Scope.addFunction("length", new LengthFunction());
        SymbolTable.Scope.addFunction("isString", new IsStringFunction());
        SymbolTable.Scope.addFunction("isInt", new IsIntFunction());
        SymbolTable.Scope.addFunction("isFloat", new IsFloatFunction());
        SymbolTable.Scope.addFunction("isBoolean", new IsBooleanFunction());
        SymbolTable.Scope.addFunction("isList", new IsListFunction());
        SymbolTable.Scope.addFunction("isDate", new IsDateFunction());
        SymbolTable.Scope.addFunction("addDays", new AddDaysFunction());
        SymbolTable.Scope.addFunction("addMonths", new AddMonthsFunction());
        SymbolTable.Scope.addFunction("before", new BeforeFunction());
        SymbolTable.Scope.addFunction("time", new TimeFunction());
        SymbolTable.Scope.addFunction("currenttime", new CurrentTimeFunction());
        SymbolTable.Scope.addFunction("read", new ReadFunction());
        SymbolTable.Scope.addFunction("merge", new MergeFunction());
        SymbolTable.Scope.addFunction("sortTime", new SortTimeFunction());
        SymbolTable.Scope.addFunction("sortData", new SortDataFunction());
        SymbolTable.Scope.addFunction("recentTimeItem", new RecentTimeItemFunction());
        SymbolTable.Scope.addFunction("oldestTimeItem", new OldestTimeItemFunction());
        SymbolTable.Scope.addFunction("within", new WithinFunction());
        SymbolTable.Scope.addFunction("readInitialEncounter", new ReadInitialEncounterFunction());
        SymbolTable.Scope.addFunction("readLatestEncounter", new ReadLatestEncounterFunction());
    }

    public static String parseType(String value) {
        int length = value.length();
        boolean isInt = true;
        boolean isFloat = true;
        for (int i = 0; i < length; i++) {
            if (Character.isLetter(value.charAt(i))) {
                isInt = false;
                isFloat = false;
            } else if (!Character.isDigit(value.charAt(i))) {
                isInt = false;
            }
        }
        if (isInt == false && isFloat == false) {
            return "string";
        } else if (isInt == true) {
            return "int";
        } else {
            return "float";
        }
    }
}
